package concurrency.semaphore;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * Created by Tzachi on 18/10/2014.
 * Suppose we have the following code:
 * class Foo {
 * public:
 * A (.....); /* If A is called, a new thread will be created and the corresponding function will be executed.
 * B(.....); /* same as above
 * C(.....); /* same as above
 * }
 * <p>
 * Foo f;
 * f.A(.....);
 * f.B(.....);
 * f.C(.....);
 * <p>
 * i) Suppose we have the following code to use class Foo. We do not know how the threads will be scheduled in the OS.
 * Foo f;
 * f.A(.....); f.B(.....); f.C(.....);
 * f.A(.....); f.B(.....); f.C(.....);
 * Can you design a mechanism to make sure that all the methods will be executed in sequence?
 */
public class ForceOrder {

    private final Semaphore s_a = new Semaphore(1);
    private final Semaphore s_b = new Semaphore(1);
    private final Semaphore s_c = new Semaphore(1);

    public ForceOrder() throws InterruptedException {
        s_a.acquire();
        s_b.acquire();
    }

    public void A() throws InterruptedException {
        s_c.acquire();
        // function code
        System.out.println(Thread.currentThread().getId() + " : I'm in A");
        s_a.release();
    }

    public void B() throws InterruptedException {
        s_a.acquire();
        // function code
        System.out.println(Thread.currentThread().getId() + " : I'm in B");
        s_b.release();
    }

    public void C() throws InterruptedException {
        s_b.acquire();
        // function code
        System.out.println(Thread.currentThread().getId() + " : I'm in C");
        s_c.release();
    }

    public static void main(String[] args) throws InterruptedException {
        ForceOrder forceOrder = new ForceOrder();
        ExecutorService pool = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 5; i++) {
            pool.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    forceOrder.A();
                    forceOrder.B();
                    forceOrder.C();
                    return null;
                }
            });
        }
    }
}
